docker基本使用(三)-- Dockerfile

Dockerfile

制作docker镜像分为两种类型:从已有的docker image中导出新的镜像,还有就是通过编写Dockerfile文件制作镜像,一般推荐后者,简单、快速、易维护。

注意事项

  1. 在制作Dockerfile时,必须要在特有的一个工作目录(一个空目录)中,比如: /opt/docker(以下简称顶级目录);
  2. 这个目录中有一个Dockerfile文件,文件首字母必须大写;
  3. 如果我们在制作过程中需要很多打包好的文件,你必须把这些文件放在此目录中,比如:/opt/docker/nginx-libs.tar.gz、/opt/docker/zip/unzip.tar.gz,说白就是以当前目录作为顶级目录;
  4. 制作镜像中如有不需要的文件或目录,可在顶级目录下创建一个.dockeringore,把不需要的文件写在这里;

常用命令

FROM

指定当前做的镜像是基于哪个已有镜像为基础,也是最重要的一个且是Dockerfile文件中的第一个非注释行,用于为镜像文件构建过程中指定的基础镜像,后续命令运行基于此基准镜像提供的运行环境。

1
2
3
4
FROM <repository>[:<tag>]或
FROM <repository>@<digest>
- <repository>: 指定作为base image的名称;
- <tag>: base image标签,为可选项,忽略时默认为latest;
  • 示例

    1
    FROM busybox:latest
LABLE

之前docker版本使用MAINTANIER(用于让Dockerfile制作者提供更详细的本人信息),现在已被LABLE标签替换,在这里指定Dockerfile的元数据

1
LABLE <key>=<value> <key>=<value> <key>=<value>...
  • 示例

    1
    LABLE maintainer="duanruijun <duanruijun.github.io>"
COPY

用于从Docker主机复制文件至新创建的镜像文件

1
2
3
4
5
6
7
8
9
10
11
12
COPY <src>...<dec> 或
COPY [<src>,..<dec>]
- <src>: 要复制的源文件或目录,支持使用通配符;
- <dec>: 目标路径,即正在创建的image的文件系统路径;建议<dec>使用绝对路径,否则,COPY指定则以WORKDIR为起始路径;

注意:在路径中有空白字符时,通常使用第二种格式

文件复制准则:
- <src>必须是build上下文中的路径,不能是其父目录中的文件
- 如果<src>是目录,则其内部文件或子目录会被递归复制,但<src>自身目录不会被复制
- 如果指定了多个<src>,或者<src>中使用了通配符,则<dec>必须是一个目录,且必须以/结尾
- 如果<dec>事先不存在,它将会被自动创建,这包括其父目录路径
  • 示例

    1
    COPY index.html /data/web/html/
制作一个简单镜像

通过以上三个命令就可以简单的制作一个docker镜像了:

1
2
3
4
5
[root@192 docker]# cat /opt/docker/Dockerfile 
# Description: test image
FROM busybox:latest
LABEL maintainer="duanruijun <duanruijun.github.io>"
COPY index.html /data/web/html/

由于这里是有了COPY命令,我们需要在/opt/docker目录下新建一个index.html的测试文件,创建一个简单的内容:

1
2
[root@192 docker]# cat /opt/docker/index.html 
<h1>Busybox http server.</h1>

运行命令docker build完成一个镜像的制作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@192 tmp1]# docker build ./ -t tinyhttp:v0.1-1
Sending build context to Docker daemon 3.072kB
Error response from daemon: Dockerfile parse error line 3: unknown instruction: LABLE
[root@192 tmp1]# vim index.html^C
[root@192 tmp1]# vim Dockerfile
[root@192 tmp1]# docker build ./ -t tinyhttp:v0.1-1
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM busybox:latest
latest: Pulling from library/busybox
e2334dd9fee4: Pull complete
Digest: sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12
Status: Downloaded newer image for busybox:latest
---> be5888e67be6
Step 2/3 : LABEL maintainer="duanruijun <drj8588@126.com>"
---> Running in 6099746c50a0
Removing intermediate container 6099746c50a0
---> 246515611329
Step 3/3 : COPY index.html /data/web/html/
---> 0d605439655c
Successfully built 0d605439655c
Successfully tagged tinyhttp:v0.1-1

命令参数说明:

  • build: 构建命令
  • ./: 生成路径,这里是当前路径
  • -t: 参数,给生成的镜像打个tag

通过docker image查看镜像

1
2
3
[root@192 docker]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
tinyhttp v0.1-1 0d605439655c 7 minutes ago 1.22MB

最后运行一个容器指定这个镜像查看是否有index.html这个文件

1
2
3
[root@192 docker]# docker run --name tinyweb1 --rm tinyhttp:v0.1-1 cat /data/web/html/index.html

<h1>Busybox http server.</h1>

说明文件已经打包到镜像里了

ADD

类似于COPY指令,ADD支持使用TAR文件或URL路径

1
2
3
4
5
6
7
8
ADD <src>...<dec> 或
ADD ["<src>",.."<dec>"]

操作准则
- 同COPY命令
- 如果<src>为URL且不为/结尾,则<src>指定的文件将被下载并直接被创建为<dec>;如果<dec>以/结尾,则文件名URL指定的文件将被直接下载并保存为<dec>/<filename>
- 如果<src>是一个本地操作系统上的一个压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令;然而通过URL获取的tar文件将不会自动展开
- 如果<src>有多个,或其间接或直接使用了通配符,则<dec>必须是一个以/结尾的目录路径;如果<dec>不以/结尾,则其被视作为一个普通文件,<src>的内容将被直接写入到<dec>
  • 示例

    1
    ADD http://nginx.org/download/nginx-1.15.2.tar.gz /usr/local/src/
WORKDIR

用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定工作目录

1
2
3
WORKDIR <dirpath>
- 在Dockerfile中,WORKDIR指定可出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径
- 另外,WORKDIR也可以调用ENV指定定义的变量
  • 示例

    1
    2
    WORKDIR /usr/local/
    ADD http://nginx.org/download/nginx-1.15.2.tar.gz ./src/
VOLUME

用于在一个image中创建一个挂载点目录,以挂载Docker host上的卷或其他容器上的卷

1
2
3
4
VOLUME <mountpoint> 或
VOLUME ["<mountpoint>"]

如果挂载点目录路径下此前文件已存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中
  • 示例

    1
    VOLUME /data/mysql/

通过docker build命令构建镜像后查看是否挂载成功:

1
2
3
4
[root@192 docker]# docker run --name tinyweb1 --rm tinyhttp:v0.1-3 mount
...
/dev/sda5 on /data/mysql type xfs (rw,relatime,attr2,inode64,noquota)
...

也可以通过docker inspect命令查看是否挂载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@192 docker]# docker run --name tinyweb1 --rm tinyhttp:v0.1-3 sleep 60
...
"Mounts": [
{
"Type": "volume",
"Name": "708aa2b16d3183d1f68dc8244d8a76f8d91e52cc4934946be544f84e01034b07",
"Source": "/var/lib/docker/volumes/708aa2b16d3183d1f68dc8244d8a76f8d91e52cc4934946be544f84e01034b07/_data",
"Destination": "/data/mysql",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
...
EXPOSE

用于为容器打开指定的监听端口以实现与外部通信

1
2
3
4
5
EXPOSE <port>[/<protolcol>] [<port>[/<protolcol>]...]
<protolcol>: 用于指定传输层协议,可为tcp和udp二者之一,默认使用tcp协议

EXPOSE指令可一次指定多个端口:
EXPOSE 11211/tcp 11211/tdp
  • 示例

    1
    EXPOSE 80/tcp

通过docker build构建后测试

1
docker run --name tinyweb1 --rm tinyhttp:v0.1-4 /bin/httpd -f -h /data/web/html

-f: httpd服务运行在前台

-h: 指定httpd的web目录

通过docker inspect tinyweb1获取到容器tinyweb1对外IP地址,就可以访问测试了:

1
2
3
4
5
6
7
8
9
10
11
[root@192 docker]# docker inspect tinyweb1
...
"Networks": {
...
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
...

[root@192 ~]# curl 172.17.0.2
<h1>Busybox http server.</h1>

此时,只是通过curl命令能够正常访问,但是通过docker port tinyweb1查看暴露的端口为空:

1
2
[root@192 docker]# docker port tinyweb1
[root@192 docker]#

这里需要在运行容器时添加-P(大写)就可以查看了:

1
2
3
4
[root@192 docker]# docker run --name tinyweb1 --rm -P tinyhttp:v0.1-4 /bin/httpd -f -h /data/web/html

[root@192 docker]# docker port tinyweb1
80/tcp -> 0.0.0.0:5000

这样在局域网中访问docker host的5000端口就可以访问容器中的http服务信息了

ENV

用于为镜像定义所需要的环境变量,并可被Dockerfile文件中位于其后的其他指令(如ENV、ADD、COPY等)所调用

调用格式为\$variable_name或\${variable_name}

1
2
3
4
5
ENV <key> <value> 或
ENV <key>=<value>...
- 第一种格式中,<key>之后的所有内容均会被视作为<value>中的组成部分,因此,一次只能设置一个变量
- 第二种格式中,可以设置多个变量,每个变量为“<key>=<value>”的键值对,如果<value>中报班空格,可以以(\)反斜线进行转义,也可通过<value>加引号进行标识,另外,反斜线也可用于续行
- 定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能
  • 示例

    1
    2
    3
    4
    ENV DOC_ROOT=/data/web/html/ \
    WEB_SERVER_PACKAGE="nginx-1.15.2"

    COPY index.html ${DOC_ROOT-/data/web/html/} # ${variable_name:-...} 默认值

通过docker build…完成后进行run操作查看传到容器中的变量信息:

1
2
3
4
5
6
[root@192 docker]# docker run --name tinyweb1 --rm tinyhttp:v0.1-5 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=5c9821659155
DOC_ROOT=/data/web/html/
WEB_SERVER_PACKAGE=nginx-1.15.2
HOME=/root

也可以在run的时候通过-e选项给容器中传参达到另一种效果:

1
2
3
4
5
6
7
[root@192 docker]# docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE="nginx-1.15.1" tinyhttp:v0.1-5 printenv

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d6ec808401dc
WEB_SERVER_PACKAGE=nginx-1.15.1
DOC_ROOT=/data/web/html/
HOME=/root
RUN

用于指定docker build过程中运行的程序,其可以是任何命令

1
2
3
4
5
6
RUN <command> 或
RUN ["<executable>","<param1>","<param2>"]

第一种格式中,<command>通常是一个shell命令,且以"/bin/sh -c"来运行它,这意味着此进程的Pid不为1,不能接受Unix信号,因此,当使用docker stop <container>命令停止容器时,此进程接收不到SIGTERM信号;
第二种语法格式中的参数是一个JSON格式的数组,其中<executable>为要运行的命令,后面的<paramN>是传递给命令的选项或参数;然而,此种格式指定的命令不会以"/bin/sh -c"来发起,因此常见的shell操作如替换以及通配符(*?)替换将不会进行;不过,如果要运行的命令依赖于此shell的话,可以将其替换为类似下面的格式:
RUN ["/bin/bash", "-c", "<executable>", "<param1>"]
  • 示例

    1
    2
    RUN cd /usr/local/src && \
    tar -x nginx.1.15.2.tar.gz
CMD

类似于RUN指令,CMD也可用于运行任何命令或应用程序,不过,二者运行的时间点不同

RUN指令运行于镜像文件的构建过程中,而CMD运行于基于Dockerfile构建出的新镜像文件启动的一个容器时

CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run命令行选项所覆盖

在Dockerfile中可以存在多个CMD指令,但仅最后一个生效

1
2
3
4
5
6
CMD <command> 或
CMD ["<executable>", "<param1>","<param2>"] 或
CMD ["<param1>","<param2>"]

前两种语法格式意义同RUN
第三种则用于为ENTRYPOINT指令提供默认参数
  • 示例

    1
    CMD /bin/httpd -f -h ...
ENTRYPOINT

类似于CMD指令的功能,用于为容器指定默认的运行程序,从而使得容器像一个单独的可执行程序

与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当成参数传递给ENTRYPOINT指定的程序

不过,docker run命令指定的–entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序

1
2
3
4
5
ENTRYPOINT <command>
ENTRYPOINT ["<executable>", "<param1>","<param2>"]

docker run命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后作为其参数使用
Dockerfile中也可以指定多个ENTRYPOINT,但是只有最后一个指令生效
  • 示例

    1
    ENTRYPOINT /bin/httpd -f -h ...
USER

用于指定运行image时的或运行Dockerfile中的任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID

默认情况下,container运行身份为root

1
2
3
USER <UID>|<UserName>

需要主要的是,<UID>可以为任意数字,但实践中必须为/etc/passwd中某用户的有效UID,否则,docker run将运行失败
  • 示例

    1
    USER nginx
HEALCHECK

检查CMD command指令健康状态

  • 示例

    1
    HEALTHCHECK --start-period=3s CMD wget -O - -q http://0.0.0.0/
SHELL

运行程序默认的shell程序,比如:Linux的[“/bin/sh”, “-c”],Windows的[“cmd”, “/s”]

ARG

在docker build中传递参数

1
ARG auth="sunshine"
  • 示例

    1
    docker build --build-arg auth="duanruijun" -t IMAGE_NAME ./
ONBUILD

用于在Dockerfile中定义一个触发器

Dockerfile用于build镜像文件,此镜像文件亦可作为base image被另一个Dockerfile用作FROM指令的参数,并以之构建新的镜像文件

在后面的这个Dockerfile中的FROM指令在build过程中被执行时,将会“触发”创建其base image的Dockerfile文件中的ONBUILD指令定义的触发器

1
2
3
4
5
ONBUILD <INSTRUCTION>

尽管任何指令都可注册成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM指令
使用包含ONBUILD的Dockerfile构建的镜像应该使用特殊标签,例如:ruby:2.0-onbuild
在ONBUILD指令中使用ADD或COPY时应该格外小心,因为新构建过程的上下文在缺少指定的源文件时会失败
  • 示例

    1
    ONBUILD ADD http://nginx.org/download/nginx.1.15.2.tar.gz /usr/local/src/

好的,以上就是Dockerfile中用到的所有常用指令。